1//////////////////////////////////////////////////////////////////////
  2// LibFile: strings.scad
  3//   String manipulation and formatting functions.
  4// Includes:
  5//   include <BOSL2/std.scad>
  6// FileGroup: Data Management
  7// FileSummary: String manipulation functions.
  8// FileFootnotes: STD=Included in std.scad
  9//////////////////////////////////////////////////////////////////////
 10
 11
 12// Section: Extracting substrings
 13
 14// Function: substr()
 15// Synopsis: Returns a substring from a string.
 16// Topics: Strings
 17// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
 18// Usage:
 19//   newstr = substr(str, [pos], [len]);
 20// Description:
 21//   Returns a substring from a string start at position `pos` with length `len`, or
 22//   if `len` isn't given, the rest of the string.
 23// Arguments:
 24//   str = string to operate on
 25//   pos = starting index of substring, or vector of first and last position.  Default: 0
 26//   len = length of substring, or omit it to get the rest of the string.  If len is zero or less then the emptry string is returned.
 27// Example:
 28//   substr("abcdefg",3,3);     // Returns "def"
 29//   substr("abcdefg",2);       // Returns "cdefg"
 30//   substr("abcdefg",len=3);   // Returns "abc"
 31//   substr("abcdefg",[2,4]);   // Returns "cde"
 32//   substr("abcdefg",len=-2);  // Returns ""
 33function substr(str, pos=0, len=undef) =
 34    is_list(pos) ? _substr(str, pos[0], pos[1]-pos[0]+1) :
 35    len == undef ? _substr(str, pos, len(str)-pos) :
 36    _substr(str,pos,len);
 37
 38function _substr(str,pos,len,substr="") =
 39    len <= 0 || pos>=len(str) ? substr :
 40    _substr(str, pos+1, len-1, str(substr, str[pos]));
 41
 42
 43// Function: suffix()
 44// Synopsis: Returns the last few characters of a string.
 45// Topics: Strings
 46// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
 47// Usage:
 48//   newstr = suffix(str,len);
 49// Description:
 50//   Returns the last `len` characters from the input string `str`.
 51//   If `len` is longer than the length of `str`, then the entirety of `str` is returned.
 52// Arguments:
 53//   str = The string to get the suffix of.
 54//   len = The number of characters of suffix to get.
 55function suffix(str,len) =
 56    len>=len(str)? str : substr(str, len(str)-len,len);
 57
 58
 59
 60// Section: String Searching
 61
 62
 63// Function: str_find()
 64// Synopsis: Finds a substring in a string.
 65// Topics: Strings
 66// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
 67// Usage:
 68//   ind = str_find(str,pattern,[last=],[all=],[start=]);
 69// Description:
 70//   Searches input string `str` for the string `pattern` and returns the index or indices of the matches in `str`.
 71//   By default `str_find()` returns the index of the first match in `str`.  If `last` is true then it returns the index of the last match.
 72//   If the pattern is the empty string the first match is at zero and the last match is the last character of the `str`.
 73//   If `start` is set then the search begins at index start, working either forward and backward from that position.  If you set `start`
 74//   and `last` is true then the search will find the pattern if it begins at index `start`.  If no match exists, returns `undef`.
 75//   If you set `all` to true then `str_find()` returns all of the matches in a list, or an empty list if there are no matches.
 76// Arguments:
 77//   str = String to search.
 78//   pattern = string pattern to search for
 79//   ---
 80//   last = set to true to return the last match. Default: false
 81//   all = set to true to return all matches as a list.  Overrides last.  Default: false
 82//   start = index where the search starts
 83// Example:
 84//   str_find("abc123def123abc","123");   // Returns 3
 85//   str_find("abc123def123abc","b");     // Returns 1
 86//   str_find("abc123def123abc","1234");  // Returns undef
 87//   str_find("abc","");                  // Returns 0
 88//   str_find("abc123def123", "123", start=4);     // Returns 9
 89//   str_find("abc123def123abc","123",last=true);  // Returns 9
 90//   str_find("abc123def123abc","b",last=true);    // Returns 13
 91//   str_find("abc123def123abc","1234",last=true); // Returns undef
 92//   str_find("abc","",last=true);                 // Returns 3
 93//   str_find("abc123def123", "123", start=8, last=true));  // Returns 3
 94//   str_find("abc123def123abc","123",all=true);   // Returns [3,9]
 95//   str_find("abc123def123abc","b",all=true);     // Returns [1,13]
 96//   str_find("abc123def123abc","1234",all=true);  // Returns []
 97//   str_find("abc","",all=true);                  // Returns [0,1,2]
 98function str_find(str,pattern,start=undef,last=false,all=false) =
 99    all? _str_find_all(str,pattern) :
100    let( start = first_defined([start,last?len(str)-len(pattern):0]) )
101    pattern==""? start :
102    last? _str_find_last(str,pattern,start) :
103    _str_find_first(str,pattern,len(str)-len(pattern),start);
104
105function _str_find_first(str,pattern,max_sindex,sindex) =
106    sindex<=max_sindex && !substr_match(str,sindex, pattern)?
107        _str_find_first(str,pattern,max_sindex,sindex+1) :
108        (sindex <= max_sindex ? sindex : undef);
109
110function _str_find_last(str,pattern,sindex) =
111    sindex>=0 && !substr_match(str,sindex, pattern)?
112        _str_find_last(str,pattern,sindex-1) :
113        (sindex >=0 ? sindex : undef);
114
115function _str_find_all(str,pattern) =
116    pattern == "" ? count(len(str)) :
117    [for(i=[0:1:len(str)-len(pattern)]) if (substr_match(str,i,pattern)) i];
118
119// Function: substr_match()
120// Synopsis: Returns true if the string `pattern` matches the string `str`.
121// Topics: Strings
122// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
123// Usage
124//   bool = substr_match(str,start,pattern);
125// Description:
126//   Returns true if the string `pattern` matches the string `str` starting
127//   at `str[start]`.  If the string is too short for the pattern, or
128//   `start` is out of bounds – either negative or beyond the end of the
129//   string – then substr_match returns false.
130// Arguments:
131//   str = String to search
132//   start = Starting index for search in str
133//   pattern = String pattern to search for
134// Examples:
135//   substr_match("abcde",2,"cd");   // Returns true
136//   substr_match("abcde",2,"cx");   // Returns false
137//   substr_match("abcde",2,"cdef"); // Returns false
138//   substr_match("abcde",-2,"cd");  // Returns false
139//   substr_match("abcde",19,"cd");  // Returns false
140//   substr_match("abc",1,"");       // Returns true
141
142//
143//    This is carefully optimized for speed.  Precomputing the length
144//    cuts run time in half when the string is long.  Two other string
145//    comparison methods were slower.
146function substr_match(str,start,pattern) =
147     len(str)-start <len(pattern)? false
148   : _substr_match_recurse(str,start,pattern,len(pattern));
149
150function _substr_match_recurse(str,sindex,pattern,plen,pindex=0,) =
151    pindex < plen && pattern[pindex]==str[sindex]
152       ? _substr_match_recurse(str,sindex+1,pattern,plen,pindex+1)
153       : (pindex==plen);
154
155
156// Function: starts_with()
157// Synopsis: Returns true if the string starts with a given substring.
158// Topics: Strings
159// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
160// Usage:
161//    bool = starts_with(str,pattern);
162// Description:
163//    Returns true if the input string `str` starts with the specified string pattern, `pattern`.
164//    Otherwise returns false.
165// Arguments:
166//   str = String to search.
167//   pattern = String pattern to search for.
168// Example:
169//   starts_with("abcdef","abc");  // Returns true
170//   starts_with("abcdef","def");  // Returns false
171//   starts_with("abcdef","");     // Returns true
172function starts_with(str,pattern) = substr_match(str,0,pattern);
173
174
175// Function: ends_with()
176// Synopsis: Returns true if the string ends with a given substring.
177// Topics: Strings
178// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
179// Usage:
180//    bool = ends_with(str,pattern);
181// Description:
182//    Returns true if the input string `str` ends with the specified string pattern, `pattern`.
183//    Otherwise returns false.
184// Arguments:
185//   str = String to search.
186//   pattern = String pattern to search for.
187// Example:
188//   ends_with("abcdef","def");  // Returns true
189//   ends_with("abcdef","de");   // Returns false
190//   ends_with("abcdef","");     // Returns true
191function ends_with(str,pattern) = substr_match(str,len(str)-len(pattern),pattern);
192
193
194
195// Function: str_split()
196// Synopsis: Splits a longer string wherever a given substring occurs.
197// Topics: Strings
198// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
199// Usage:
200//   string_list = str_split(str, sep, [keep_nulls]);
201// Description:
202//   Breaks an input string into substrings using a separator or list of separators.  If keep_nulls is true
203//   then two sequential separator characters produce an empty string in the output list.  If keep_nulls is false
204//   then no empty strings are included in the output list.
205//   .
206//   If sep is a single string then each character in sep is treated as a delimiting character and the input string is
207//   split at every delimiting character.  Empty strings can occur whenever two delimiting characters are sequential.
208//   If sep is a list of strings then the input string is split sequentially using each string from the list in order.
209//   If keep_nulls is true then the output will have length equal to `len(sep)+1`, possibly with trailing null strings
210//   if the string runs out before the separator list.
211// Arguments:
212//   str = String to split.
213//   sep = a string or list of strings to use for the separator
214//   keep_nulls = boolean value indicating whether to keep null strings in the output list.  Default: true
215// Example:
216//   str_split("abc+def-qrs*iop","*-+");     // Returns ["abc", "def", "qrs", "iop"]
217//   str_split("abc+*def---qrs**iop+","*-+");// Returns ["abc", "", "def", "", "", "qrs", "", "iop", ""]
218//   str_split("abc      def"," ");          // Returns ["abc", "", "", "", "", "", "def"]
219//   str_split("abc      def"," ",keep_nulls=false);  // Returns ["abc", "def"]
220//   str_split("abc+def-qrs*iop",["+","-","*"]);     // Returns ["abc", "def", "qrs", "iop"]
221//   str_split("abc+def-qrs*iop",["-","+","*"]);     // Returns ["abc+def", "qrs*iop", "", ""]
222function str_split(str,sep,keep_nulls=true) =
223    !keep_nulls ? _remove_empty_strs(str_split(str,sep,keep_nulls=true)) :
224    is_list(sep) ? _str_split_recurse(str,sep,i=0,result=[]) :
225    let( cutpts = concat([-1],sort(flatten(search(sep, str,0))),[len(str)]))
226    [for(i=[0:len(cutpts)-2]) substr(str,cutpts[i]+1,cutpts[i+1]-cutpts[i]-1)];
227
228function _str_split_recurse(str,sep,i,result) =
229    i == len(sep) ? concat(result,[str]) :
230    let(
231        pos = search(sep[i], str),
232        end = pos==[] ? len(str) : pos[0]
233    )
234    _str_split_recurse(
235        substr(str,end+1),
236        sep, i+1,
237        concat(result, [substr(str,0,end)])
238    );
239
240function _remove_empty_strs(list) =
241    list_remove(list, search([""], list,0)[0]);
242
243
244
245// Section: String modification
246
247
248// Function: str_join()
249// Synopsis: Joints a list of strings into a single string.
250// Topics: Strings
251// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
252// Usage:
253//   str = str_join(list, [sep]);
254// Description:
255//   Returns the concatenation of a list of strings, optionally with a
256//   separator string inserted between each string on the list.
257// Arguments:
258//   list = list of strings to concatenate
259//   sep = separator string to insert.  Default: ""
260// Example:
261//   str_join(["abc","def","ghi"]);        // Returns "abcdefghi"
262//   str_join(["abc","def","ghi"], " + ");  // Returns "abc + def + ghi"
263function str_join(list,sep="",_i=0, _result="") =
264    _i >= len(list)-1 ? (_i==len(list) ? _result : str(_result,list[_i])) :
265    str_join(list,sep,_i+1,str(_result,list[_i],sep));
266
267
268
269
270// Function: str_strip()
271// Synopsis: Strips given leading and trailing characters from a string.
272// Topics: Strings
273// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
274// Usage:
275//   str = str_strip(s,c,[start],[end]);
276// Description:
277//   Takes a string `s` and strips off all leading and/or trailing characters that exist in string `c`.
278//   By default strips both leading and trailing characters.  If you set start or end to true then
279//   it will strip only the leading or trailing characters respectively.  If you set start
280//   or end to false then it will strip only lthe trailing or leading characters.
281// Arguments:
282//   s = The string to strip leading or trailing characters from.
283//   c = The string of characters to strip.
284//   start = if true then strip leading characters
285//   end = if true then strip trailing characters
286// Example:
287//   str_strip("--##--123--##--","#-");  // Returns: "123"
288//   str_strip("--##--123--##--","-");   // Returns: "##--123--##"
289//   str_strip("--##--123--##--","#");   // Returns: "--##--123--##--"
290//   str_strip("--##--123--##--","#-",end=true);  // Returns: "--##--123"
291//   str_strip("--##--123--##--","-",end=true);   // Returns: "--##--123--##"
292//   str_strip("--##--123--##--","#",end=true);   // Returns: "--##--123--##--"
293//   str_strip("--##--123--##--","#-",start=true); // Returns: "123--##--"
294//   str_strip("--##--123--##--","-",start=true);  // Returns: "##--123--##--"
295//   str_strip("--##--123--##--","#",start=true);  // Returns: "--##--123--##--"
296
297function _str_count_leading(s,c,_i=0) =
298    (_i>=len(s)||!in_list(s[_i],[each c]))? _i :
299    _str_count_leading(s,c,_i=_i+1);
300
301function _str_count_trailing(s,c,_i=0) =
302    (_i>=len(s)||!in_list(s[len(s)-1-_i],[each c]))? _i :
303    _str_count_trailing(s,c,_i=_i+1);
304
305function str_strip(s,c,start,end) =
306  let(
307      nstart = (is_undef(start) && !end) ? true : start,
308      nend = (is_undef(end) && !start) ? true : end,
309      startind = nstart ? _str_count_leading(s,c) : 0,
310      endind = len(s) - (nend ? _str_count_trailing(s,c) : 0)
311  )
312  substr(s,startind, endind-startind);
313
314
315
316// Function: str_pad()
317// Synopsis: Pads a string to a given length.
318// Topics: Strings
319// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
320// Usage:
321//   padded = str_pad(str, length, char, [left]);
322// Description:
323//   Pad the given string `str` with to length `length` with the specified character,
324//   which must be a length 1 string.  If left is true then pad on the left, otherwise
325//   pad on the right.  If the string is longer than the specified length the full string
326//   is returned unchanged.
327// Arguments:
328//   str = string to pad
329//   length = length to pad to
330//   char = character to pad with.  Default: " " (space)
331//   left = if true, pad on the left side.  Default: false
332function str_pad(str,length,char=" ",left=false) =
333  assert(is_str(str))
334  assert(is_str(char) && len(char)==1, "char must be a single character string")
335  assert(is_bool(left))
336  let(
337    padding = str_join(repeat(char,length-len(str)))
338  )
339  left ? str(padding,str) : str(str,padding);
340
341
342
343// Function: str_replace_char()
344// Synopsis: Replace given chars in a string with another substring.
345// Topics: Strings
346// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip()
347// Usage:
348//   newstr = str_replace_char(str, char, replace);
349// Description:
350//   Replace every occurence of `char` in the input string with the string `replace` which
351//   can be any string.
352function str_replace_char(str,char,replace) =
353   assert(is_str(str))
354   assert(is_str(char) && len(char)==1, "Search pattern 'char' must be a single character string")
355   assert(is_str(replace))
356   str_join([for(c=str) c==char ? replace : c]);
357
358
359// Function: downcase()
360// Synopsis: Lowercases all characters in a string.
361// Topics: Strings
362// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip(), upcase(), downcase()
363// Usage:
364//   newstr = downcase(str);
365// Description:
366//   Returns the string with the standard ASCII upper case letters A-Z replaced
367//   by their lower case versions.
368// Arguments:
369//   str = String to convert.
370// Example:
371//   downcase("ABCdef");   // Returns "abcdef"
372function downcase(str) =
373    str_join([for(char=str) let(code=ord(char)) code>=65 && code<=90 ? chr(code+32) : char]);
374
375
376// Function: upcase()
377// Synopsis: Uppercases all characters in a string.
378// Topics: Strings
379// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip(), upcase(), downcase()
380// Usage:
381//   newstr = upcase(str);
382// Description:
383//   Returns the string with the standard ASCII lower case letters a-z replaced
384//   by their upper case versions.
385// Arguments:
386//   str = String to convert.
387// Example:
388//   upcase("ABCdef");   // Returns "ABCDEF"
389function upcase(str) =
390    str_join([for(char=str) let(code=ord(char)) code>=97 && code<=122 ? chr(code-32) : char]);
391
392
393// Section: Random strings
394
395// Function: rand_str()
396// Synopsis: Create a randomized string.
397// Topics: Strings
398// See Also: suffix(), str_find(), substr_match(), starts_with(), ends_with(), str_split(), str_join(), str_strip(), upcase(), downcase()
399// Usage:
400//    str = rand_str(n, [charset], [seed]);
401// Description:
402//    Produce a random string of length `n`.  If you give a string `charset` then the
403//    characters of the random string are drawn from that list, weighted by the number
404//    of times each character appears in the list.  If you do not give a character set
405//    then the string is generated with characters ranging from 0 to z (based on
406//    character code).
407function rand_str(n, charset, seed) =
408  is_undef(charset)? str_join([for(c=rand_int(48,122,n,seed)) chr(c)])
409                   : str_join([for(i=rand_int(0,len(charset)-1,n,seed)) charset[i]]);
410
411
412
413// Section: Parsing strings into numbers
414
415// Function: parse_int()
416// Synopsis: Parse an integer from a string.
417// Topics: Strings
418// See Also: parse_int(), parse_float(), parse_frac(), parse_num()
419// Usage:
420//   num = parse_int(str, [base])
421// Description:
422//   Converts a string into an integer with any base up to 16.  Returns NaN if
423//   conversion fails.  Digits above 9 are represented using letters A-F in either
424//   upper case or lower case.
425// Arguments:
426//   str = String to convert.
427//   base = Base for conversion, from 2-16.  Default: 10
428// Example:
429//   parse_int("349");        // Returns 349
430//   parse_int("-37");        // Returns -37
431//   parse_int("+97");        // Returns 97
432//   parse_int("43.9");       // Returns nan
433//   parse_int("1011010",2);  // Returns 90
434//   parse_int("13",2);       // Returns nan
435//   parse_int("dead",16);    // Returns 57005
436//   parse_int("CEDE", 16);   // Returns 52958
437//   parse_int("");           // Returns 0
438function parse_int(str,base=10) =
439    str==undef ? undef :
440    len(str)==0 ? 0 :
441    let(str=downcase(str))
442    str[0] == "-" ? -_parse_int_recurse(substr(str,1),base,len(str)-2) :
443    str[0] == "+" ?  _parse_int_recurse(substr(str,1),base,len(str)-2) :
444    _parse_int_recurse(str,base,len(str)-1);
445
446function _parse_int_recurse(str,base,i) =
447    let(
448        digit = search(str[i],"0123456789abcdef"),
449        last_digit = digit == [] || digit[0] >= base ? (0/0) : digit[0]
450    ) i==0 ? last_digit :
451    _parse_int_recurse(str,base,i-1)*base + last_digit;
452
453
454// Function: parse_float()
455// Synopsis: Parse a float from a string.
456// Topics: Strings
457// See Also: parse_int(), parse_float(), parse_frac(), parse_num()
458// Usage:
459//   num = parse_float(str);
460// Description:
461//   Converts a string to a floating point number.  Returns NaN if the
462//   conversion fails.
463// Arguments:
464//   str = String to convert.
465// Example:
466//   parse_float("44");       // Returns 44
467//   parse_float("3.4");      // Returns 3.4
468//   parse_float("-99.3332"); // Returns -99.3332
469//   parse_float("3.483e2");  // Returns 348.3
470//   parse_float("-44.9E2");  // Returns -4490
471//   parse_float("7.342e-4"); // Returns 0.0007342
472//   parse_float("");         // Returns 0
473function parse_float(str) =
474    str==undef ? undef :
475    len(str) == 0 ? 0 :
476    in_list(str[1], ["+","-"]) ? (0/0) : // Don't allow --3, or +-3
477    str[0]=="-" ? -parse_float(substr(str,1)) :
478    str[0]=="+" ?  parse_float(substr(str,1)) :
479    let(esplit = str_split(str,"eE") )
480    len(esplit)==2 ? parse_float(esplit[0]) * pow(10,parse_int(esplit[1])) :
481    let( dsplit = str_split(str,["."]))
482    parse_int(dsplit[0])+parse_int(dsplit[1])/pow(10,len(dsplit[1]));
483
484
485// Function: parse_frac()
486// Synopsis: Parse a float from a fraction string.
487// Topics: Strings
488// See Also: parse_int(), parse_float(), parse_frac(), parse_num()
489// Usage:
490//   num = parse_frac(str,[mixed=],[improper=],[signed=]);
491// Description:
492//   Converts a string fraction to a floating point number.  A string fraction has the form `[-][# ][#/#]` where each `#` is one or more of the
493//   digits 0-9, and there is an optional sign character at the beginning.
494//   The full form is a sign character and an integer, followed by exactly one space, followed by two more
495//   integers separated by a "/" character.  The leading integer and
496//   space can be omitted or the trailing fractional part can be omitted.  If you set `mixed` to false then the leading integer part is not
497//   accepted and the input must include a slash.  If you set `improper` to false then the fractional part must be a proper fraction, where
498//   the numerator is smaller than the denominator.  If you set `signed` to false then the leading sign character is not permitted.
499//   The empty string evaluates to zero.  Any invalid string evaluates to NaN.
500// Arguments:
501//   str = String to convert.
502//   ---
503//   mixed = set to true to accept mixed fractions, false to reject them.  Default: true
504//   improper = set to true to accept improper fractions, false to reject them.  Default: true
505//   signed = set to true to accept a leading sign character, false to reject.  Default: true
506// Example:
507//   parse_frac("3/4");     // Returns 0.75
508//   parse_frac("-77/9");   // Returns -8.55556
509//   parse_frac("+1/3");    // Returns 0.33333
510//   parse_frac("19");      // Returns 19
511//   parse_frac("2 3/4");   // Returns 2.75
512//   parse_frac("-2 12/4"); // Returns -5
513//   parse_frac("");        // Returns 0
514//   parse_frac("3/0");     // Returns inf
515//   parse_frac("0/0");     // Returns nan
516//   parse_frac("-77/9",improper=false);   // Returns nan
517//   parse_frac("-2 12/4",improper=false); // Returns nan
518//   parse_frac("-2 12/4",signed=false);   // Returns nan
519//   parse_frac("-2 12/4",mixed=false);    // Returns nan
520//   parse_frac("2 1/4",mixed=false);      // Returns nan
521function parse_frac(str,mixed=true,improper=true,signed=true) =
522    str == undef ? undef
523  : len(str)==0 ? 0
524  : str[0]==" " ? NAN
525  : signed && str[0]=="-" ? -parse_frac(substr(str,1),mixed=mixed,improper=improper,signed=false)
526  : signed && str[0]=="+" ?  parse_frac(substr(str,1),mixed=mixed,improper=improper,signed=false)
527  : mixed && (str_find(str," ")!=undef || str_find(str,"/")==undef)?   // Mixed allowed and there is a space or no slash
528        let(whole = str_split(str,[" "]))
529        _parse_int_recurse(whole[0],10,len(whole[0])-1) + parse_frac(whole[1], mixed=false, improper=improper, signed=false)
530  : let(split = str_split(str,"/"))
531    len(split)!=2 ? NAN
532  : let(
533        numerator =  _parse_int_recurse(split[0],10,len(split[0])-1),
534        denominator = _parse_int_recurse(split[1],10,len(split[1])-1)
535    )
536    !improper && numerator>=denominator? NAN
537  : denominator<0 ? NAN
538  : numerator/denominator;
539
540
541// Function: parse_num()
542// Synopsis: Parse a float from a decimal or fraction string.
543// Topics: Strings
544// See Also: parse_int(), parse_float(), parse_frac(), parse_num()
545// Usage:
546//   num = parse_num(str);
547// Description:
548//   Converts a string to a number.  The string can be either a fraction (two integers separated by a "/") or a floating point number.
549//   Returns NaN if the conversion fails.
550// Example:
551//   parse_num("3/4");    // Returns 0.75
552//   parse_num("3.4e-2"); // Returns 0.034
553function parse_num(str) =
554    str == undef ? undef :
555    let( val = parse_frac(str) )
556    val == val ? val :
557    parse_float(str);
558
559
560
561
562// Section: Formatting numbers into strings
563
564// Function: format_int()
565// Synopsis: Formats an integer into a string, with possible leading zeros.
566// Topics: Strings
567// See Also: format_int(), format_fixed(), format_float(), format()
568// Usage:
569//   str = format_int(i, [mindigits]);
570// Description:
571//   Formats an integer number into a string.  This can handle larger numbers than `str()`.
572// Arguments:
573//   i = The integer to make a string of.
574//   mindigits = If the number has fewer than this many digits, pad the front with zeros until it does.  Default: 1.
575// Example:
576//   str(123456789012345);  // Returns "1.23457e+14"
577//   format_int(123456789012345);  // Returns "123456789012345"
578//   format_int(-123456789012345);  // Returns "-123456789012345"
579function format_int(i,mindigits=1) =
580    i<0? str("-", format_int(-i,mindigits)) :
581    let(i=floor(i), e=floor(log(i)))
582    i==0? str_join([for (j=[0:1:mindigits-1]) "0"]) :
583    str_join(
584        concat(
585            [for (j=[0:1:mindigits-e-2]) "0"],
586            [for (j=[e:-1:0]) str(floor(i/pow(10,j)%10))]
587        )
588    );
589
590
591// Function: format_fixed()
592// Synopsis: Formats a float into a string with a fixed number of decimal places.
593// Topics: Strings
594// See Also: format_int(), format_fixed(), format_float(), format()
595// Usage:
596//   s = format_fixed(f, [digits]);
597// Description:
598//   Given a floating point number, formats it into a string with the given number of digits after the decimal point.
599// Arguments:
600//   f = The floating point number to format.
601//   digits = The number of digits after the decimal to show.  Default: 6
602function format_fixed(f,digits=6) =
603    assert(is_int(digits))
604    assert(digits>0)
605    is_list(f)? str("[",str_join(sep=", ", [for (g=f) format_fixed(g,digits=digits)]),"]") :
606    str(f)=="nan"? "nan" :
607    str(f)=="inf"? "inf" :
608    f<0? str("-",format_fixed(-f,digits=digits)) :
609    assert(is_num(f))
610    let(
611        sc = pow(10,digits),
612        scaled = floor(f * sc + 0.5),
613        whole = floor(scaled/sc),
614        part = floor(scaled-(whole*sc))
615    ) str(format_int(whole),".",format_int(part,digits));
616
617
618// Function: format_float()
619// Synopsis: Formats a float into a string with a given number of significant digits.
620// Topics: Strings
621// See Also: format_int(), format_fixed(), format_float(), format()
622// Usage:
623//   str = format_float(f,[sig]);
624// Description:
625//   Formats the given floating point number `f` into a string with `sig` significant digits.
626//   Strips trailing `0`s after the decimal point.  Strips trailing decimal point.
627//   If the number can be represented in `sig` significant digits without a mantissa, it will be.
628//   If given a list of numbers, recursively prints each item in the list, returning a string like `[3,4,5]`
629// Arguments:
630//   f = The floating point number to format.
631//   sig = The number of significant digits to display.  Default: 12
632// Example:
633//   format_float(PI,12);  // Returns: "3.14159265359"
634//   format_float([PI,-16.75],12);  // Returns: "[3.14159265359, -16.75]"
635function format_float(f,sig=12) =
636    assert(is_int(sig))
637    assert(sig>0)
638    is_list(f)? str("[",str_join(sep=", ", [for (g=f) format_float(g,sig=sig)]),"]") :
639    f==0? "0" :
640    str(f)=="nan"? "nan" :
641    str(f)=="inf"? "inf" :
642    f<0? str("-",format_float(-f,sig=sig)) :
643    assert(is_num(f))
644    let(
645        e = floor(log(f)),
646        mv = sig - e - 1
647    ) mv == 0? format_int(floor(f + 0.5)) :
648    (e<-sig/2||mv<0)? str(format_float(f*pow(10,-e),sig=sig),"e",e) :
649    let(
650        ff = f + pow(10,-mv)*0.5,
651        whole = floor(ff),
652        part = floor((ff-whole) * pow(10,mv))
653    )
654    str_join([
655        str(whole),
656        str_strip(end=true,
657            str_join([
658                ".",
659                format_int(part, mindigits=mv)
660            ]),
661            "0."
662        )
663    ]);
664
665
666/// Function: _format_matrix()
667/// Usage:
668///   _format_matrix(M, [sig], [sep], [eps])
669/// Description:
670///   Convert a numerical matrix into a matrix of strings where every column
671///   is the same width so it will display in neat columns when printed.
672///   Values below eps will display as zero.  The matrix can include nans, infs
673///   or undefs and the rows can be different lengths.
674/// Arguments:
675///   M = numerical matrix to convert
676///   sig = significant digits to display.  Default: 4
677//    sep = number of spaces between columns or a text string to separate columns.  Default: 1
678///   eps = values smaller than this are shown as zero.  Default: 1e-9
679function _format_matrix(M, sig=4, sep=1, eps=1e-9) =
680   let(
681       figure_dash = chr(8210),
682       space_punc = chr(8200),
683       space_figure = chr(8199),
684       sep = is_num(sep) && sep>=0 ? str_join(repeat(space_figure,sep))
685           : is_string(sep) ? sep
686           : assert(false,"Invalid separator: must be a string or positive integer giving number of spaces"),
687       strarr=
688         [for(row=M)
689             [for(entry=row)
690                 let(
691                     text = is_undef(entry) ? "und"
692                          : !is_num(entry) ? str_join(repeat(figure_dash,2))
693                          : abs(entry) < eps ? "0"             // Replace hyphens with figure dashes
694                          : str_replace_char(format_float(entry, sig),"-",figure_dash),
695                     have_dot = is_def(str_find(text, "."))
696                 )
697                 // If the text lacks a dot we add a space the same width as a dot to
698                 // maintain alignment
699                 str(have_dot ? "" : space_punc, text)
700             ]
701         ],
702       maxwidth = max([for(row=M) len(row)]),
703       // Find maximum length for each column.  Some entries in a column may be missing.
704       maxlen = [for(i=[0:1:maxwidth-1])
705                    max(
706                         [for(j=idx(M)) i>=len(M[j]) ? 0 : len(strarr[j][i])])
707                ],
708       padded =
709         [for(row=strarr)
710            str_join([for(i=idx(row))
711                            let(
712                                extra = ends_with(row[i],"inf") ? 1 : 0
713                            )
714                            str_pad(row[i],maxlen[i]+extra,space_figure,left=true)],sep=sep)]
715    )
716    padded;
717
718
719
720// Function: format()
721// Synopsis: Formats multiple values into a string with a given format.
722// Topics: Strings
723// See Also: format_int(), format_fixed(), format_float(), format()
724// Usage:
725//   s = format(fmt, vals);
726// Description:
727//   Given a format string and a list of values, inserts the values into the placeholders in the format string and returns it.
728//   Formatting placeholders have the following syntax:
729//   - A leading `{` character to show the start of the placeholder.
730//   - An integer index into the `vals` list to specify which value should be formatted at that place. If not given, the first placeholder will use index `0`, the second will use index `1`, etc.
731//   - An optional `:` separator to indicate that what follows if a formatting specifier.  If not given, no formatting info follows.
732//   - An optional `-` character to indicate that the value should be left justified if the value needs field width padding.  If not given, right justification is used.
733//   - An optional `0` character to indicate that the field should be padded with `0`s.  If not given, spaces will be used for padding.
734//   - An optional integer field width, which the value should be padded to.  If not given, no padding will be performed.
735//   - An optional `.` followed by an integer precision length, for specifying how many digits to display in numeric formats.  If not give, 6 digits is assumed.
736//   - An optional letter to indicate the formatting style to use.  If not given, `s` is assumed, which will do it's generic best to format any data type.
737//   - A trailing `}` character to show the end of the placeholder.
738//   .
739//   Formatting styles, and their effects are as follows:
740//   - `s`: Converts the value to a string with `str()` to display.  This is very generic.
741//   - `i` or `d`: Formats numeric values as integers.
742//   - `f`: Formats numeric values with the precision number of digits after the decimal point.  NaN and Inf are shown as `nan` and `inf`.
743//   - `F`: Formats numeric values with the precision number of digits after the decimal point.  NaN and Inf are shown as `NAN` and `INF`.
744//   - `g`: Formats numeric values with the precision number of total significant digits.  NaN and Inf are shown as `nan` and `inf`.  Mantissas are demarked by `e`.
745//   - `G`: Formats numeric values with the precision number of total significant digits.  NaN and Inf are shown as `NAN` and `INF`.  Mantissas are demarked by `E`.
746//   - `b`: If the value logically evaluates as true, it shows as `true`, otherwise `false`.
747//   - `B`: If the value logically evaluates as true, it shows as `TRUE`, otherwise `FALSE`.
748// Arguments:
749//   fmt = The formatting string, with placeholders to format the values into.
750//   vals = The list of values to format.
751// Example(NORENDER):
752//   format("The value of {} is {:.14f}.", ["pi", PI]);  // Returns: "The value of pi is 3.14159265358979."
753//   format("The value {1:f} is known as {0}.", ["pi", PI]);  // Returns: "The value 3.141593 is known as pi."
754//   format("We use a very small value {1:.6g} as {0}.", ["EPSILON", EPSILON]);  // Returns: "We use a very small value 1e-9 as EPSILON."
755//   format("{:-5s}{:i}{:b}", ["foo", 12e3, 5]);  // Returns: "foo  12000true"
756//   format("{:-10s}{:.3f}", ["plecostamus",27.43982]);  // Returns: "plecostamus27.440"
757//   format("{:-10.9s}{:.3f}", ["plecostamus",27.43982]);  // Returns: "plecostam 27.440"
758function format(fmt, vals) =
759    let(
760        parts = str_split(fmt,"{")
761    ) str_join([
762        for(i = idx(parts))
763        let(
764            found_brace = i==0 || [for (c=parts[i]) if(c=="}") c] != [],
765            err = assert(found_brace, "Unbalanced { in format string."),
766            p = i==0? [undef,parts[i]] : str_split(parts[i],"}"),
767            fmta = p[0],
768            raw = p[1]
769        ) each [
770            assert(i<99)
771            is_undef(fmta)? "" : let(
772                fmtb = str_split(fmta,":"),
773                num = is_digit(fmtb[0])? parse_int(fmtb[0]) : (i-1),
774                left = fmtb[1][0] == "-",
775                fmtb1 = default(fmtb[1],""),
776                fmtc = left? substr(fmtb1,1) : fmtb1,
777                zero = fmtc[0] == "0",
778                lch = fmtc==""? "" : fmtc[len(fmtc)-1],
779                hastyp = is_letter(lch),
780                typ = hastyp? lch : "s",
781                fmtd = hastyp? substr(fmtc,0,len(fmtc)-1) : fmtc,
782                fmte = str_split((zero? substr(fmtd,1) : fmtd), "."),
783                wid = parse_int(fmte[0]),
784                prec = parse_int(fmte[1]),
785                val = assert(num>=0&&num<len(vals)) vals[num],
786                unpad = typ=="s"? (
787                        let( sval = str(val) )
788                        is_undef(prec)? sval :
789                        substr(sval, 0, min(len(sval)-1, prec))
790                    ) :
791                    (typ=="d" || typ=="i")? format_int(val) :
792                    typ=="b"? (val? "true" : "false") :
793                    typ=="B"? (val? "TRUE" : "FALSE") :
794                    typ=="f"? downcase(format_fixed(val,default(prec,6))) :
795                    typ=="F"? upcase(format_fixed(val,default(prec,6))) :
796                    typ=="g"? downcase(format_float(val,default(prec,6))) :
797                    typ=="G"? upcase(format_float(val,default(prec,6))) :
798                    assert(false,str("Unknown format type: ",typ)),
799                padlen = max(0,wid-len(unpad)),
800                padfill = str_join([for (i=[0:1:padlen-1]) zero? "0" : " "]),
801                out = left? str(unpad, padfill) : str(padfill, unpad)
802            )
803            out, raw
804        ]
805    ]);
806
807
808
809// Section: Checking character class
810
811// Function: is_lower()
812// Synopsis: Returns true if all characters in the string are lowercase.
813// Topics: Strings
814// See Also: is_lower(), is_upper(), is_digit(), is_hexdigit(), is_letter()
815// Usage:
816//   x = is_lower(s);
817// Description:
818//   Returns true if all the characters in the given string are lowercase letters. (a-z)
819function is_lower(s) =
820    assert(is_string(s))
821    s==""? false :
822    len(s)>1? all([for (v=s) is_lower(v)]) :
823    let(v = ord(s[0])) (v>=ord("a") && v<=ord("z"));
824
825
826// Function: is_upper()
827// Synopsis: Returns true if all characters in the string are uppercase.
828// Topics: Strings
829// See Also: is_lower(), is_upper(), is_digit(), is_hexdigit(), is_letter()
830// Usage:
831//   x = is_upper(s);
832// Description:
833//   Returns true if all the characters in the given string are uppercase letters. (A-Z)
834function is_upper(s) =
835    assert(is_string(s))
836    s==""? false :
837    len(s)>1? all([for (v=s) is_upper(v)]) :
838    let(v = ord(s[0])) (v>=ord("A") && v<=ord("Z"));
839
840
841// Function: is_digit()
842// Synopsis: Returns true if all characters in the string are decimal digits.
843// Topics: Strings
844// See Also: is_lower(), is_upper(), is_digit(), is_hexdigit(), is_letter()
845// Usage:
846//   x = is_digit(s);
847// Description:
848//   Returns true if all the characters in the given string are digits. (0-9)
849function is_digit(s) =
850    assert(is_string(s))
851    s==""? false :
852    len(s)>1? all([for (v=s) is_digit(v)]) :
853    let(v = ord(s[0])) (v>=ord("0") && v<=ord("9"));
854
855
856// Function: is_hexdigit()
857// Synopsis: Returns true if all characters in the string are hexidecimal digits.
858// Topics: Strings
859// See Also: is_lower(), is_upper(), is_digit(), is_hexdigit(), is_letter()
860// Usage:
861//   x = is_hexdigit(s);
862// Description:
863//   Returns true if all the characters in the given string are valid hexadecimal digits. (0-9 or a-f or A-F))
864function is_hexdigit(s) =
865    assert(is_string(s))
866    s==""? false :
867    len(s)>1? all([for (v=s) is_hexdigit(v)]) :
868    let(v = ord(s[0]))
869    (v>=ord("0") && v<=ord("9")) ||
870    (v>=ord("A") && v<=ord("F")) ||
871    (v>=ord("a") && v<=ord("f"));
872
873
874// Function: is_letter()
875// Synopsis: Returns true if all characters in the string are letters.
876// Topics: Strings
877// See Also: is_lower(), is_upper(), is_digit(), is_hexdigit(), is_letter()
878// Usage:
879//   x = is_letter(s);
880// Description:
881//   Returns true if all the characters in the given string are standard ASCII letters. (A-Z or a-z)
882function is_letter(s) =
883    assert(is_string(s))
884    s==""? false :
885    all([for (v=s) is_lower(v) || is_upper(v)]);
886
887
888
889
890
891// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap